﻿using Godot;
using KludgeBox;

namespace VectorSurvivors.GameWorld;

[GlobalClass]
public partial class Character : Entity
{
    [ExportGroup("Internal References")]
    [Export] [NotNull] public Sprite2D Sprite { get; private set; }
    [Export] [NotNull] public CharacterBody Body { get; private set; }
    [Export] [NotNull] public Area2D HitBox { get; private set; }
    [Export] [NotNull] public CharacterController Controller { get; private set; }
    
    [ExportGroup("Config")]
    [Export] public double MaxSpeed { get; set; } = 500;
    [Export] public double AccelerationTime { get; set; } = 0.15;
    [Export] public double Friction { get; set; } = 1;
    [Export] public double RotationSpeed { get; set; } = 300; // in degree/sec
    [Export] public double MaxHp { get; set; } = 10000;
    [Export] public double Hp { get; set; } = -1; // if less than 0, then will be set to MaxHp

    public Color Color
    {
        get => Sprite.Modulate;
        set => Sprite.Modulate = value;
    }

    /// <inheritdoc />
    public override void _Ready()
    {
        base._Ready();
        NotNullChecker.CheckProperties(this);
        
        if (Hp < 0)
        {
            Hp = MaxHp;
        }
        Hp = Mathf.Min(MaxHp, Hp);
    }

    
    /// <inheritdoc />
    public override void _Process(double delta)
    {
        base._Process(delta);
        
        FollowBody(delta);
        RotateToAimPos(delta);
    }

    /// <inheritdoc />
    public override void _PhysicsProcess(double delta)
    {
        base._PhysicsProcess(delta);
        ProcessMovement(Controller.InputVector, delta);
    }

    
    private const double NetworkDelta = 1d / 20;
    private const double PhysicsDelta = 1d / 60;
    private void FollowBody(double delta)
    {
        double smoothingFactor = delta / PhysicsDelta;
        Vector2 requiredMovement = Body.Position - Position;
        Vector2 smoothedMovement = requiredMovement * smoothingFactor;

        Position += smoothedMovement * 0.5;
    }
    
    private void ProcessMovement(Vector2 inputVector, double delta)
    {
        var friction = Friction;
		
        var speed = Body.Velocity.Length();
        var acceleration = MaxSpeed / AccelerationTime * friction;
        var breaking = Mathf.Min(speed, acceleration * delta);

        speed += acceleration * delta * 2;

        var velDir = Body.Velocity.Normalized();
        Body.Velocity += inputVector * acceleration * delta * 2 - velDir * breaking;

        Body.Velocity = Body.Velocity.LimitLength(MaxSpeed);

        Body.MoveAndSlide();
    }
    
    public void RotateToAimPos(double delta)
    {
        //Куда хотим повернуться
        double targetAngle = GetAngleToAimPos();
        //На какой угол надо повернуться (знак указывает направление)
        double deltaAngleToTargetAngel = Mathf.AngleDifference(Rotation - Mathf.Pi / 2, targetAngle);
        //Только направление (-1, 0, 1)
        double directionToTargetAngel = Mathf.Sign(deltaAngleToTargetAngel);
        //Максимальная скорость поворота (за секунду)
        double rotationSpeedRad = Mathf.DegToRad(RotationSpeed);
        //Максимальная скорость поворота (за прошедшее время)
        rotationSpeedRad *= delta;
        //Если надо повернуться на угол меньший максимальной скорости, то обрезаем скорость, чтобы повернуться ровно в цель
        rotationSpeedRad = Mathf.Min(rotationSpeedRad, Mathf.Abs(deltaAngleToTargetAngel));
        //Добавляем к скорости поворота направление, чтобы поворачивать в сторону цели
        rotationSpeedRad *= directionToTargetAngel;
        //Поворачиваемся на угол
        Rotation += rotationSpeedRad;
    }
    
    public double GetAngleToAimPos()
    {
        // Получаем текущую позицию мыши
        var mousePos = Controller.AimPos;//GetGlobalMousePosition();
        // Вычисляем вектор направления от объекта к мыши
        var mouseDir = GlobalPosition.DirectionTo(mousePos);
        // Вычисляем направление от объекта к мыши
        return mouseDir.Angle();
    }
}